﻿#include "coordinate2dwidget.h"

#include <QActionGroup>
#include <QApplication>
#include <QFile>
#include <QKeyEvent>
#include <QMenu>
#include <QPainter>




Coordinate2DWidget::Coordinate2DWidget(QWidget *parent)
    : QWidget{parent}
{

    this->setAttribute(Qt::WA_StyledBackground, true);
    this->setFocusPolicy(Qt::StrongFocus);

    QFile file(":/qss/style.qss");
    file.open(QFile::ReadOnly);
    if(file.isOpen()){
        QString qss = file.readAll();
        this->setStyleSheet(qss);
    }
    file.close();

    m_pMenu = new QMenu(this);
    m_actAddKey = new QAction("Add Key", this);
    m_actDeleteKey = new QAction("Delete Key", this);

    connect(m_actAddKey, &QAction::triggered, this, &Coordinate2DWidget::onAddKey);
    connect(m_actDeleteKey, &QAction::triggered, this, &Coordinate2DWidget::onDeleteKey);

    m_grpTangentMode = new QActionGroup(this);
    m_grpTangentMode->setExclusive(true);

    connect(m_grpTangentMode, &QActionGroup::triggered, this, &Coordinate2DWidget::onTangentModeSelect);

#define XX(name) \
    m_act##name = new QAction(#name, this); \
    m_act##name->setCheckable(true); \
    m_grpTangentMode->addAction(m_act##name);

    LIST_ALL_TANGENT_MODE
#undef XX

    m_curves.emplace_back(new SplineCurve());
    m_activeCurve = m_curves.front();
}

void Coordinate2DWidget::init()
{
    if(!m_hasInit){
        m_offset.setX(m_marginCooridate);
        m_offset.setY(this->height() - 1 - m_marginCooridate);
        m_hasInit = true;
    }
}

QPoint Coordinate2DWidget::transformBack(const QPointF &pt)
{
    qreal scale = m_separationSpace / m_unit * m_zoom;
    qreal realX = pt.x() * scale + m_offset.x();
    qreal realY = m_offset.y() - pt.y() * scale;
    return QPointF(realX, realY).toPoint();
}

QPointF Coordinate2DWidget::transformTo(const QPoint &pt)
{
    qreal scale =  m_unit / m_separationSpace / m_zoom;
    qreal realX = (pt.x() - m_offset.x()) * scale;
    qreal realY = (m_offset.y() - pt.y()) * scale;
    return QPointF(realX, realY);
}

void Coordinate2DWidget::paintEvent(QPaintEvent *event)
{
    init();
    //绘制坐标
    drawAxisNum();
    //绘制选择框
    drawSelectionRect();
    //绘制可视区域内的曲线和点
    drawCurves();
}

void Coordinate2DWidget::keyPressEvent(QKeyEvent *event)
{
    m_keyBtn |= event->key();
    int space = m_separationSpace * m_zoom;
    switch (event->key()) {
    case Qt::Key_D:
        m_offset.setX(m_offset.x() + space);
        repaint();
        break;
    case Qt::Key_A:
        m_offset.setX(m_offset.x() - space);
        repaint();
        break;
    case Qt::Key_W:
        m_offset.setY(m_offset.y() - space);
        repaint();
        break;
    case Qt::Key_S:
        m_offset.setY(m_offset.y() + space);
        repaint();
        break;

    case Qt::Key_Delete:
        onDeleteKey();
        break;
    default:
        break;
    }

}

void Coordinate2DWidget::keyReleaseEvent(QKeyEvent *event)
{
    m_keyBtn &= (~event->key());
    if(event->key() == Qt::Key_Shift && (m_mouseBtn & Qt::LeftButton)){
        update();
    }
}

void Coordinate2DWidget::mousePressEvent(QMouseEvent *event)
{

    m_mouseBtn |= event->button();
    m_pressPosition.setX(std::min(std::max(event->pos().x(), m_ChartRect.left()), m_ChartRect.right()));
    m_pressPosition.setY(std::min(std::max(event->pos().y(), m_ChartRect.top()), m_ChartRect.bottom()));
    m_movePosition = m_pressPosition;
    if(m_mouseBtn & Qt::LeftButton){
        //按下Shift增量选择
        bool isAddedSelect = (m_keyBtn & Qt::Key_Shift) != 0;
        //单选
        selectPoints(true, isAddedSelect);
        update();
    }
}

void Coordinate2DWidget::mouseReleaseEvent(QMouseEvent *event)
{
    m_mouseBtn &= (~event->button());
    if(event->button() == Qt::LeftButton && (m_keyBtn & Qt::Key_Shift)){
        update();
    }
}

void Coordinate2DWidget::mouseMoveEvent(QMouseEvent *event)
{

    m_movePosition.setX(std::min(std::max(event->pos().x(), m_ChartRect.left()), m_ChartRect.right()));
    m_movePosition.setY(std::min(std::max(event->pos().y(), m_ChartRect.top()), m_ChartRect.bottom()));
    if(m_mouseBtn & Qt::LeftButton){
        if(m_keyBtn & Qt::Key_Shift){
            //框选，增量选择
            selectPoints(false, true);
        }else{
            bool isSingleSelect = m_activeCurve && m_activeCurve->countSelectPoint() == 1;
            if(isSingleSelect){
                movePoints();
            }else{
                moveViewRect();
            }
            m_pressPosition = m_movePosition;
        }
        update();
    }else if(m_mouseBtn & Qt::MiddleButton){
        bool hasSelectPoint = m_activeCurve && m_activeCurve->countSelectPoint() > 0;
        if(hasSelectPoint){
            movePoints();
            m_pressPosition = m_movePosition;
            update();
        }
    }

}

void Coordinate2DWidget::wheelEvent(QWheelEvent *event)
{
    m_movePosition = this->mapFromGlobal(cursor().pos());
    if(!m_ChartRect.contains(m_movePosition))
        return;

    QPointF offset = transformTo(m_movePosition);
    qreal zoomSpeed = 0.1;
    if(event->angleDelta().y() > 0){
        m_zoom += zoomSpeed;
    }else{
        m_zoom -= zoomSpeed;
    }
    if(m_zoom >= 2.0){
        m_zoom = 1.0;
        m_unit /= 2;
    }else if(m_zoom <= 0.5){
        m_zoom = 1.0;
        m_unit *= 2;
    }

    m_offset -= (transformBack(offset) - m_movePosition);

    update();
}

void Coordinate2DWidget::contextMenuEvent(QContextMenuEvent *event)
{
    m_movePosition = this->mapFromGlobal(cursor().pos());
    if(!m_ChartRect.contains(m_movePosition))
        return;
    Q_UNUSED(event);
    m_pMenu->clear();

    m_pMenu->addAction(m_actAddKey);
    if(m_activeCurve && m_activeCurve->countSelectPoint() > 0){
        m_pMenu->addAction(m_actDeleteKey);

        m_pMenu->addSeparator();
#define XX(name) \
        {\
            m_pMenu->addAction(m_act##name);\
            bool isChecked = m_activeCurve && m_activeCurve->checkSelectPointTangentMode(TangentMode::name); \
            m_act##name->setChecked(isChecked);\
        }

    LIST_ALL_TANGENT_MODE
#undef XX
        m_pMenu->addSeparator();

    }
    m_pMenu->exec(QCursor::pos());
}

void Coordinate2DWidget::drawAxisNum()
{
    QPainter painter(this);
    int width = this->width();
    int height = this->height();

    m_ChartRect.setRect(m_marginCooridate, m_marginCooridate, width - m_marginCooridate * 2, height - m_marginCooridate * 2);


    int fontSizeLarge = 16;
    int fontSize = 12;

    int space = m_separationSpace * m_zoom;
    m_viewRect.setLeft(m_ChartRect.left() - m_offset.x());
    m_viewRect.setRight(m_ChartRect.right() - m_offset.x());
    int start = m_viewRect.left() / space;
    int end = m_viewRect.right() / space;
    m_viewRect.setLeft(m_viewRect.left() / space * m_unit);
    m_viewRect.setRight(m_viewRect.right() / space * m_unit);
    for(int i = start; i <= end; ++ i){
        int offset = i * space + m_offset.x();
        if(offset < m_ChartRect.left()) continue;
        if(offset > m_ChartRect.right()) break;
        QPoint pt1(offset, m_ChartRect.top());
        QPoint pt2(offset, m_ChartRect.bottom());
        QRectF textRect1(pt1.x() - space / 2, 0, space, m_marginCooridate);
        QRectF textRect2(pt1.x() - space / 2, m_ChartRect.bottom(), space, m_marginCooridate);
        QPen pen = painter.pen();
        QFont font = painter.font();
        if(i % 10 == 0){
            pen.setColor(QColor(55, 55, 55));
            pen.setWidth(2);
            painter.setPen(pen);
            font.setPixelSize(fontSizeLarge);
            painter.setFont(font);

        }else{
            pen.setColor(QColor(22, 22, 22));
            pen.setWidth(1);
            painter.setPen(pen);
            font.setPixelSize(fontSize);
            painter.setFont(font);
        }
        painter.drawLine(pt1, pt2);
        qreal val = i * m_unit;
        painter.drawText(textRect1, Qt::AlignCenter, QString::number(val));
        painter.drawText(textRect2, Qt::AlignCenter, QString::number(val));


    }

    m_viewRect.setTop(m_offset.y() - m_ChartRect.bottom());
    m_viewRect.setBottom(m_offset.y() - m_ChartRect.top());
    start = m_viewRect.top() / space;
    end = m_viewRect.bottom() / space;
    //qDebug() << "y: [" << start << ", " << end << "]";
    m_viewRect.setTop(m_viewRect.top() / space * m_unit);
    m_viewRect.setBottom(m_viewRect.bottom() / space * m_unit);
    for(int i = start; i <= end; ++ i){
        int offset = m_offset.y() - i * space;
        if(offset < m_ChartRect.top()) break;
        if(offset > m_ChartRect.bottom()) continue;
        QPoint pt1(m_ChartRect.left(), offset);
        QPoint pt2(m_ChartRect.right(), offset);
        QRectF textRect1(0, pt1.y() - space / 2, m_marginCooridate, space);
        QRectF textRect2(m_ChartRect.right(), pt1.y() - space / 2, m_marginCooridate, space);
        QPen pen = painter.pen();
        QFont font = painter.font();
        if(i % 10 == 0){
            pen.setColor(QColor(55, 55, 55));
            pen.setWidth(2);
            painter.setPen(pen);
            font.setPixelSize(fontSizeLarge);
            painter.setFont(font);

        }else{
            pen.setColor(QColor(22, 22, 22));
            pen.setWidth(1);
            painter.setPen(pen);
            font.setPixelSize(fontSize);
            painter.setFont(font);
        }
        painter.drawLine(pt1, pt2);
        qreal val = i * m_unit;
        painter.drawText(textRect1, Qt::AlignCenter, QString::number(val));
        painter.drawText(textRect2, Qt::AlignCenter, QString::number(val));

    }

    QPen pen = painter.pen();
    pen.setColor(QColor(200, 55, 55));
    pen.setWidth(3);
    painter.setPen(pen);
    painter.drawRect(m_ChartRect);

    // qDebug() << "x:[" << m_viewRect.left() << ", " << m_viewRect.right() << "], "
    //          << "y:[" << m_viewRect.top() << ", " << m_viewRect.bottom() << "]";

}

void Coordinate2DWidget::drawCurves()
{
    for(auto& curve:m_curves){
        drawCurve(curve);
        curve->visitAllPoints([&](const SplinePoint::ptr& pt){
            if(isInViewRect(pt->getPoint())){
                drawPoint(pt);
            }
        });
    }
}

void Coordinate2DWidget::drawCurve(const SplineCurve::ptr &curve)
{
    std::vector<QPointF> arr = curve->getCurve(m_viewRect.left(), m_viewRect.right());
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setClipping(true);
    painter.setClipRect(m_ChartRect, Qt::IntersectClip);
    QPen pen = painter.pen();
    pen.setColor(QColor(55, 55, 200));
    pen.setWidth(4);
    painter.setPen(pen);
    QPoint lastPoint;
    int count = 0;
    for(const auto& pt:arr){
        QPoint point = transformBack(pt);
        if(count > 0){
            painter.drawLine(lastPoint, point);
        }
        count += 1;
        lastPoint = point;
    }

}

void Coordinate2DWidget::drawPoint(const SplinePoint::ptr& sp)
{
    QPainter painter(this);
    painter.setClipping(true);
    painter.setClipRect(m_ChartRect, Qt::IntersectClip);

    QPoint realPoint = transformBack(sp->getPoint());


    QBrush brush = painter.brush();
    brush.setStyle(Qt::BrushStyle::SolidPattern);


    switch (sp->getTangentMode()) {
    case TangentMode::Linear:
    case TangentMode::Constant:
    case TangentMode::Weighted:{

        if(sp->isSelect()){
            brush.setColor(QColor(235, 224, 106));
        }else{
            brush.setColor(QColor(211, 211, 211));
        }
        painter.setBrush(brush);
        static const QPoint triangel[3] = {QPoint(-m_iconSize / 2, m_iconSize / 2), QPoint(0, -m_iconSize), QPoint(m_iconSize / 2, m_iconSize / 2)};
        painter.translate(realPoint.x(), realPoint.y());
        painter.drawPolygon(triangel, 3);

        break;
    }
    case TangentMode::Auto:
    case TangentMode::User:
    case TangentMode::Break:{

        if(sp->isSelect()){
            painter.save();
            painter.setRenderHint(QPainter::Antialiasing);
            QPoint realHandle1 = transformBack(sp->getHandle1());
            QPoint realHandle2 = transformBack(sp->getHandle2());

            QPen pen = painter.pen();
            pen.setWidth(2);
            pen.setColor(QColor(211, 145, 45));
            painter.setPen(pen);
            painter.drawLine(realHandle1, realPoint);
            painter.drawLine(realHandle2, realPoint);

            painter.restore();

            if(sp->getTangentMode() == TangentMode::Break){
                if(sp->getSelect() == SplinePoint::_Handle1){
                    brush.setColor(QColor(235, 224, 106));
                }else{
                    brush.setColor(QColor(211, 211, 211));
                }
                painter.setBrush(brush);
                painter.drawEllipse(realHandle1, m_iconSize / 2, m_iconSize / 2);
                if(sp->getSelect() == SplinePoint::_Handle2){
                    brush.setColor(QColor(235, 224, 106));
                }else{
                    brush.setColor(QColor(211, 211, 211));
                }
                painter.setBrush(brush);
                painter.drawEllipse(realHandle2, m_iconSize / 2, m_iconSize / 2);
            }else{
                if(sp->getSelect() == SplinePoint::_Handle1 || sp->getSelect() == SplinePoint::_Handle2){
                    brush.setColor(QColor(235, 224, 106));
                }else{
                    brush.setColor(QColor(211, 211, 211));
                }
                painter.setBrush(brush);
                painter.drawEllipse(realHandle1, m_iconSize / 2, m_iconSize / 2);
                painter.drawEllipse(realHandle2, m_iconSize / 2, m_iconSize / 2);
            }
        }


        if(sp->getSelect() == SplinePoint::_Point){
            brush.setColor(QColor(235, 224, 106));
        }else{
            brush.setColor(QColor(211, 211, 211));
        }
        painter.setBrush(brush);
        painter.translate(realPoint.x(), realPoint.y());
        painter.rotate(45);
        painter.translate(-m_iconSize / 2, -m_iconSize / 2);

        painter.drawRect(0, 0, m_iconSize, m_iconSize);

        break;
    }
    default:
        break;
    }



}

void Coordinate2DWidget::drawSelectionRect()
{
    if(!(m_keyBtn & Qt::Key_Shift) || !(m_mouseBtn & Qt::LeftButton))
        return;
    if(m_pressPosition == m_movePosition)
        return;
    QRectF rect(std::min(m_pressPosition.x(), m_movePosition.x()),
                std::min(m_pressPosition.y(), m_movePosition.y()),
                std::abs(m_pressPosition.x() - m_movePosition.x()),
                std::abs(m_pressPosition.y() - m_movePosition.y()));
    QPainter paint(this);
    paint.setPen(Qt::PenStyle::DashDotLine);
    paint.drawRect(rect);
}

void Coordinate2DWidget::selectPoints(bool isSingle, bool isAdded)
{

    QRectF rect;
    if(isSingle){
        //单选模式
        QPoint center(m_iconSize / 2, m_iconSize / 2);
        QPointF pt1 = transformTo(m_pressPosition - center);
        QPointF pt2 = transformTo(m_pressPosition + center);
        rect = QRectF(pt1, pt2);
    }else{
        //框选模式
        QPointF pt1 = transformTo(m_pressPosition);
        QPointF pt2 = transformTo(m_movePosition);
        rect = QRectF(std::min(pt1.x(), pt2.x()),
                      std::min(pt1.y(), pt2.y()),
                      std::abs(pt1.x() - pt2.x()),
                      std::abs(pt1.y() - pt2.y()));
    }

    for(auto& curve:m_curves){
        curve->selectPoints(rect, isAdded);
    }

}

void Coordinate2DWidget::clearSelectPoints()
{
    if(!m_activeCurve)
        return;

    m_activeCurve->clearSelectPoints();
}

void Coordinate2DWidget::movePoints()
{
    if(!m_activeCurve || m_activeCurve->countSelectPoint() == 0)
        return;

    QPointF pt = transformTo(m_offset + m_movePosition - m_pressPosition);
    m_activeCurve->moveSelectPoints(pt);
}

void Coordinate2DWidget::moveViewRect()
{
    m_offset += m_movePosition - m_pressPosition;
}

bool Coordinate2DWidget::isInViewRect(const QPointF& pt) const
{
    return m_viewRect.contains(pt);
}

void Coordinate2DWidget::onAddKey()
{
    if(!m_activeCurve)
        return;

    QPointF pt = transformTo(m_movePosition);
    m_activeCurve->addPoint(pt, SplinePoint::Select::_Point);
    update();
}

void Coordinate2DWidget::onDeleteKey()
{
    if(!m_activeCurve || m_activeCurve->countSelectPoint() == 0)
        return;
    m_activeCurve->deletePoint();
    update();
}

void Coordinate2DWidget::onTangentModeSelect(QAction* action)
{
    if(!action)
        return;
    if(!m_activeCurve)
        return;
#define XX(name) \
    if(m_act##name == action) {\
        /*qDebug() << #name;*/ \
        m_activeCurve->setTangent(TangentMode::name); \
    }
    LIST_ALL_TANGENT_MODE
#undef XX
}
